package io.github.wanggit.antrpc.client.spring;

import io.github.wanggit.antrpc.commons.annotations.RpcAutowired;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.Objects;

@Slf4j
public class DefaultCallerProxyFactory implements CallerProxyFactory {
    @Override
    public Object proxy(
            String currentBeanName,
            Object currentBean,
            ConfigurableListableBeanFactory beanFactory,
            CallerProxyMethodInterceptor callerProxyMethodInterceptor) {
        Field[] fields =
                FieldUtils.getFieldsWithAnnotation(currentBean.getClass(), RpcAutowired.class);
        if (fields.length > 0) {
            if (log.isInfoEnabled()) {
                log.info(
                        "Caller "
                                + currentBean.getClass().getName()
                                + " will create proxy object.");
            }
            return internalProxy(
                    currentBeanName, currentBean, beanFactory, callerProxyMethodInterceptor);
        }
        return currentBean;
    }

    private Object internalProxy(
            String currentBeanName,
            Object currentBean,
            ConfigurableListableBeanFactory beanFactory,
            CallerProxyMethodInterceptor callerProxyMethodInterceptor) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(currentBean.getClass());
        enhancer.setCallback(callerProxyMethodInterceptor);
        Object proxyedBean = enhancer.create();
        // 查找到Spring中所有依赖currentBean的对象，并替换已换创建的对象的依赖关系
        String[] dependentBeans = beanFactory.getDependentBeans(currentBeanName);
        for (String dependentBean : dependentBeans) {
            Object upLevelBean = beanFactory.getBean(dependentBean);
            Field[] allFields = FieldUtils.getAllFields(upLevelBean.getClass());
            if (log.isInfoEnabled()) {
                log.info(
                        upLevelBean.getClass().getName()
                                + " depends on "
                                + proxyedBean.getClass().getName()
                                + ". will set "
                                + proxyedBean.toString()
                                + " to "
                                + upLevelBean.toString());
            }
            internalSetField(
                    currentBeanName, proxyedBean.getClass(), proxyedBean, upLevelBean, allFields);
        }
        // 添加我的所有依赖
        String[] dependenciesForBeans = beanFactory.getDependenciesForBean(currentBeanName);
        Field[] allFields = FieldUtils.getAllFields(proxyedBean.getClass());
        for (String dependenciesForBean : dependenciesForBeans) {
            Object lowLevelBean = beanFactory.getBean(dependenciesForBean);
            if (log.isInfoEnabled()) {
                log.info(
                        proxyedBean.getClass().getName()
                                + " depends on "
                                + lowLevelBean.getClass().getName()
                                + ". will set "
                                + lowLevelBean.toString()
                                + " to "
                                + proxyedBean.toString());
            }
            internalSetField(
                    dependenciesForBean,
                    lowLevelBean.getClass(),
                    lowLevelBean,
                    proxyedBean,
                    allFields);
        }
        BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) beanFactory;
        beanDefinitionRegistry.removeBeanDefinition(currentBeanName);
        beanFactory.registerSingleton(currentBeanName, proxyedBean);
        return proxyedBean;
    }

    private void internalSetField(
            String beanName,
            Class<?> beanClass,
            Object valueObject,
            Object target,
            Field[] allFields) {
        for (Field field : allFields) {
            if (field.getType().isAssignableFrom(beanClass)) {
                if (field.getName().startsWith("CGLIB$")) {
                    continue;
                }
                Qualifier qualifier = field.getAnnotation(Qualifier.class);
                if (null == qualifier || Objects.equals(beanName, qualifier.value())) {
                    ReflectionUtils.makeAccessible(field);
                    ReflectionUtils.setField(field, target, valueObject);
                    if (log.isInfoEnabled()) {
                        log.info(
                                "set "
                                        + target.getClass()
                                        + "#"
                                        + field.getName()
                                        + " with "
                                        + valueObject.toString());
                    }
                }
            }
        }
    }
}
